﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;
using System.Windows;
using System.Windows.Media;
using System.Windows.Media.Imaging;

namespace Mrkz.ImageManipulation
{
    public class TImage<TPixel> : IEquatable<TImage<TPixel>> 
        where TPixel : IArithmeticOperation<TPixel>, IEquatable<TPixel>, IEquatableWithTolerance<TPixel>,IPixel,new()
    {
        internal TPixel[,] _data;
        internal TPixel _pixel=new TPixel();
        internal int _stride;
        internal int _width;
        internal int _height;
        internal Int32Rect _cropArea;
        internal double _magn_norm_factor;

        internal TImage()
        {
        }

        public Int32Rect CropArea
        {
            get { return _cropArea; }
        }

        public int Width
        {
            get { return _width; }

        }
        public int Height
        {
            get { return _height; }
        }

        public bool IsValidPixelCoords(int Row, int Col)
        {
            if (Col >= 0
                && Col < Width
                && Row >= 0
                && Row < Height)
                return true;

            return false;
        }       

        public void SetPixel(int Row, int Column, TPixel Pixel)
        {
            _data[Row, Column] = Pixel;
        }

        public TPixel GetPixel(int Row, int Column)
        {
            return _data[Row, Column];
        }

        public static TImage<TPixel> operator +(TImage<TPixel> A, TImage<TPixel> B)
        {
            if (A.Height != B.Height
                || A.Width != B.Width)
                throw new ArgumentException("Images must be same size");

            TImage<TPixel> res = ImageConverter<TPixel,TPixel>.CreateEmptyImage(A);

            for (int iR = 0; iR < A.Height; iR++)
                for (int iC = 0; iC < A.Width; iC++)
                    res.SetPixel(iR, iC,
                        A.GetPixel(iR, iC).Add(B.GetPixel(iR, iC)));

            return res;
        }

        public static TImage<TPixel> operator -(TImage<TPixel> A, TImage<TPixel> B)
        {
            if (A.Height != B.Height
                || A.Width != B.Width)
                throw new ArgumentException("Images must be same size");

            TImage<TPixel> res = ImageConverter<TPixel,TPixel>.CreateEmptyImage(A);

            for (int iR = 0; iR < A.Height; iR++)
                for (int iC = 0; iC < A.Width; iC++)
                    res.SetPixel(iR, iC,
                        A.GetPixel(iR, iC).Subtract(B.GetPixel(iR, iC)));

            return res;
        }

        public bool Equals(TImage<TPixel> other, int tolerance)
        {
            if (other._cropArea.Equals(_cropArea))
            {
                //compare all pixels
                for (int iR = 0; iR < CropArea.Height; iR++)
                    for (int iC = 0; iC < CropArea.Width; iC++)
                        if (!_data[iR, iC].Equals(other._data[iR, iC], tolerance))
                            return false;

                return true;
            }
            return false;
        }

        public bool Equals(TImage<TPixel> other)
        {
            if (other._cropArea.Equals(_cropArea))
            {
                //compare all pixels
                for (int iR = 0; iR < CropArea.Height; iR++)
                    for (int iC = 0; iC < CropArea.Width; iC++)
                        if (!_data[iR, iC].Equals(other._data[iR, iC]))
                            return false;

                return true;
            }
            return false;
        }

        internal TPixel GetPixelWithPad(int Row, int Column)
        {
            if (Row >= Height || Column >= Width)                
                return new TPixel();

            return _data[Row, Column];
        }

        public void Normalize()
        {
            if (_pixel.GetType() == typeof(ComplexPixel))
            {
                double m = 0;
                for (int i = 0; i < CropArea.Height; i++)
                    for (int j = 0; j < CropArea.Width; j++)
                    {
                        if (Math.Log(1 + _data[i, j].Red) > m)
                            m = Math.Log(1 + _data[i, j].Red);
                        if (Math.Log(1 + _data[i, j].Green) > m)
                            m = Math.Log(1 + _data[i, j].Green);
                        if (Math.Log(1 + _data[i, j].Blue) > m)
                            m = Math.Log(1 + _data[i, j].Blue);

                    }

                _magn_norm_factor = 1 / m;
            }
            else
                _magn_norm_factor = 1;

        }

        internal void SetPixelWithPad(long Row, int Column, TPixel pixel)
        {
            if (Row >= Height || Column >= Width)
                return;

            _data[Row, Column] = pixel;
        }

        public static TImage<TPixel> operator * (TImage<TPixel> a, double factor)
        {
            TImage<TPixel> r = ImageFactory<TPixel>.CreateEmptyImage(a.Height,a.Width,a.CropArea);

            for (int i = 0; i < a.CropArea.Height; i++)
                for (int j = 0; j < a.CropArea.Width; j++)
                    r._data[i,j]=a._data[i, j].Multiply(factor);

            return r;
        }
               
    }

}
